package org.gdstash.file;

import org.gdstash.util.GDConstants;
import org.gdstash.util.GDMsgFormatter;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.charset.Charset;

public class GDReader {
    public static final int XOR_BITMAP = 1431655765;
    public static final int TABLE_MULT = 39916801;
    public static final long MAX_UNSIGNED_INT = 4294967296L;
    private static int key;
    private static int[] table;
    private static byte[] buffer;
    private static int pos;

    public static int[] buildTable(final int key) {
        final int[] table = new int[256];
        int k = key;
        for (int i = 0; i < table.length; ++i) {
            k = Integer.rotateRight(k, 1);
            k *= 39916801;
            table[i] = k;
        }
        return table;
    }

    private static void updateKey(final byte[] b) {
        for (int i = 0; i < b.length; ++i) {
            int j = b[i];
            if (j < 0) {
                j += 256;
            }
            GDReader.key ^= GDReader.table[j];
        }
    }

    private static void updateKey(final int value) {
        final byte[] b = ByteBuffer.allocate(4).putInt(value).array();
        updateKey(b);
    }

    private static void updateKey(final byte value) {
        final byte[] b = {value};
        updateKey(b);
    }

    public static int readKey() throws IOException {
        int k = readEncInt(false);
        k = (GDReader.key = (k ^ 0x55555555));
        GDReader.table = buildTable(GDReader.key);
        return GDReader.key;
    }

    private static byte[] readEncByteArr(final int len) throws IOException {
        if (GDReader.pos + len > GDReader.buffer.length) {
            throw new EOFException(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_UNEXPECTED_EOF"));
        }
        final byte[] b = new byte[len];
        for (int i = 0; i < b.length; ++i) {
            b[i] = GDReader.buffer[GDReader.pos + i];
        }
        GDReader.pos += b.length;
        return b;
    }

    public static int readBlockStart(final Block block) throws IOException {
        final int i = readEncInt(true);
        block.length = readEncInt(false);
        block.end = GDReader.pos + block.length;
        return i;
    }

    public static void readBlockEnd(final Block block) throws IOException {
        if (GDReader.pos != block.end) {
            throw new IOException();
        }
        final int i = readEncInt(false);
        if (i != 0) {
            throw new IOException();
        }
    }

    public static byte readEncByte() throws IOException {
        final byte[] bArr = readEncByteArr(1);
        final byte b = bArr[0];
        bArr[0] ^= (byte) GDReader.key;
        updateKey(b);
        return bArr[0];
    }

    public static float readEncFloat(final boolean updateKey) throws IOException {
        final int i = readEncInt(updateKey);
        final float f = Float.intBitsToFloat(i);
        return f;
    }

    public static int readEncInt(final boolean updateKey) throws IOException {
        final byte[] b = readEncByteArr(4);
        final int i = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getInt();
        final int result = i ^ GDReader.key;
        if (updateKey) {
            updateKey(i);
        }
        return result;
    }

    static {
        GDReader.table = new int[256];
    }

    public static byte[] readEncStringBytes() throws IOException {
        final int len = readEncInt(true);
        if (len == 0) {
            return null;
        }
        final byte[] b = readEncByteArr(len);
        for (int i = 0; i < b.length; ++i) {
            final byte temp = b[i];
            b[i] ^= (byte) GDReader.key;
            updateKey(temp);
        }
        return b;
    }

    public static String convBytesToString(final byte[] bArr) throws IOException {
        String s = null;
        if (bArr != null) {
            s = new String(bArr, GDConstants.CHARSET_STASH);
        }
        return s;
    }

    public static String readEncWideString() throws IOException {
        int len = readEncInt(true);
        if (len == 0) {
            return null;
        }
        final StringBuilder sb = new StringBuilder(len);
        len *= 2;
        for (int i = 0; i < len; i += 2) {
            final byte b1 = readEncByte();
            final byte b2 = readEncByte();
            short s = b2;
            s <<= 8;
            final char c = (char) (b1 | s);
            sb.append(c);
        }
        final String s2 = sb.toString();
        return s2;
    }

    public static void readEncFileToBuffer(final File file) throws IOException {
        try (final FileInputStream reader = new FileInputStream(file)) {
            GDReader.buffer = new byte[(int) reader.getChannel().size()];
            GDReader.pos = 0;
            GDReader.key = 0;
            reader.read(GDReader.buffer);
            reader.close();
        } catch (IOException ex) {
            throw ex;
        }
    }

    public static byte[] readBytes(final InputStream is, final int len) throws IOException {
        final byte[] b = new byte[len];
        final int i = is.read(b);
        if (i < len) {
            throw new EOFException(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_UNEXPECTED_EOF"));
        }
        return b;
    }

    public static byte[] readBytes4(final InputStream is) throws IOException {
        return readBytes(is, 4);
    }

    public static byte readByte(final InputStream is) throws IOException {
        final byte[] b = {0};
        final int i = is.read();
        if (i == -1) {
            throw new EOFException(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_UNEXPECTED_EOF"));
        }
        return b[0] = (byte) i;
    }

    public static boolean readByteBool(final InputStream is) throws IOException {
        final byte[] b = {0};
        final int i = is.read();
        if (i == -1) {
            throw new EOFException(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_UNEXPECTED_EOF"));
        }
        return i != 0;
    }

    public static int readUByte(final InputStream is) throws IOException {
        final int i = is.read();
        if (i == -1) {
            throw new EOFException(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_UNEXPECTED_EOF"));
        }
        return i;
    }

    public static float readFloat(final InputStream is) throws IOException {
        final int i = readInt(is);
        final float f = Float.intBitsToFloat(i);
        return f;
    }

    public static int readInt(final InputStream is) throws IOException {
        final byte[] b = readBytes(is, 4);
        return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getInt();
    }

    public static long readLong(final InputStream is) throws IOException {
        final byte[] b = readBytes(is, 8);
        return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getLong();
    }

    public static int readUInt(final InputStream is) throws IOException {
        final int size = 4;
        final byte[] b = readBytes(is, size);
        int val = 0;
        for (int i = size; i > 0; --i) {
            int temp = b[i - 1];
            if (temp < 0) {
                temp += 256;
            }
            val = val * 256 + temp;
        }
        return val;
    }

    public static short readShort(final InputStream is) throws IOException {
        final int size = 2;
        final byte[] b = readBytes(is, size);
        short val = 0;
        for (int i = size; i > 0; --i) {
            int temp = b[i - 1];
            if (i != size && temp < 0) {
                temp += 256;
            }
            val = (short) (val * 256 + b[i - 1]);
        }
        return val;
    }

    public static short readUShort(final InputStream is) throws IOException {
        final int size = 2;
        final byte[] b = readBytes(is, size);
        short val = 0;
        for (int i = size; i > 0; --i) {
            int temp = b[i - 1];
            if (temp < 0) {
                temp += 256;
            }
            val = (short) (val * 256 + temp);
        }
        return val;
    }

    public static String readString(final InputStream is, final int len) throws IOException {
        final byte[] b = readBytes(is, len);
        String s = "";
        for (int i = 0; i < b.length; ++i) {
            s += Character.toString((char) b[i]);
        }
        return s;
    }

    public static String readString(final InputStream is, final int len, final Charset cs) throws IOException {
        if (len == 0) {
            return "";
        }
        final byte[] b = readBytes(is, len);
        final String s = new String(b, cs);
        return s;
    }

    public static String readString(final InputStream is) throws IOException {
        final int len = readUInt(is);
        final byte[] b = readBytes(is, len);
        String s = "";
        for (int i = 0; i < b.length; ++i) {
            s += Character.toString((char) b[i]);
        }
        return s;
    }

    public static String readStringUByte(final InputStream is) throws IOException {
        return readStringUByte(is, GDConstants.CHARSET_STASH);
    }

    public static String readStringUByte(final InputStream is, final Charset cs) throws IOException {
        final int len = readUByte(is);
        if (len == 0) {
            return null;
        }
        final byte[] b = readBytes(is, len);
        final String s = new String(b, cs);
        return s;
    }

    public static boolean getBool(final byte[] bytes, final int offset) {
        final byte[] b = getBytes(bytes, offset, 4);
        boolean val = false;
        for (int i = 0; i < b.length; ++i) {
            val = (val || b[i] != 0);
        }
        return val;
    }

    public static byte[] getBytes(final byte[] bytes, final int offset, final int len) {
        if (len <= 0) {
            return null;
        }
        final byte[] b = new byte[len];
        for (int i = 0; i < len; ++i) {
            b[i] = bytes[offset + i];
        }
        return b;
    }

    public static byte[] getBytes4(final byte[] bytes, final int offset) {
        return getBytes(bytes, offset, 4);
    }

    public static byte[] getBytes8(final byte[] bytes, final int offset) {
        return getBytes(bytes, offset, 8);
    }

    public static float getFloat(final byte[] bytes, final int offset) {
        final byte[] b = getBytes(bytes, offset, 4);
        final float val = ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getFloat();
        return val;
    }

    public static int getInt(final byte[] bytes, final int offset) {
        final byte[] b = getBytes(bytes, offset, 4);
        return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getInt();
    }

    public static long getLong(final byte[] bytes, final int offset) {
        final byte[] b = getBytes(bytes, offset, 8);
        return ByteBuffer.wrap(b).order(ByteOrder.LITTLE_ENDIAN).getLong();
    }

    public static int getUInt(final byte[] bytes, final int offset) {
        final int size = 4;
        int val = 0;
        for (int i = size; i > 0; --i) {
            int temp = bytes[offset + i - 1];
            if (temp < 0) {
                temp += 256;
            }
            val = val * 256 + temp;
        }
        return val;
    }

    public static short getShort(final byte[] bytes, final int offset) {
        final int size = 2;
        short val = 0;
        for (int i = size; i > 0; --i) {
            int temp = bytes[offset + i - 1];
            if (i != size && temp < 0) {
                temp += 256;
            }
            val = (short) (val * 256 + temp);
        }
        return val;
    }

    public static short getUShort(final byte[] bytes, final int offset) {
        final int size = 2;
        short val = 0;
        for (int i = size; i > 0; --i) {
            int temp = bytes[offset + i - 1];
            if (temp < 0) {
                temp += 256;
            }
            val = (short) (val * 256 + temp);
        }
        return val;
    }

    public static String getString(final byte[] bytes, final int offset, final int len) {
        final StringBuffer sb = new StringBuffer(len);
        for (int i = 0; i < len; ++i) {
            sb.append((char) bytes[offset + i]);
        }
        return sb.toString();
    }

    public static String getString0(final byte[] bytes, final int offset) {
        if (offset >= bytes.length) {
            return "";
        }
        final StringBuilder sb = new StringBuilder(128);
        for (int i = offset; bytes[i] != 0; ++i) {
            sb.append((char) bytes[i]);
        }
        return sb.toString();
    }

    public static byte[] getBytes(final MappedByteBuffer buffer, final int len) {
        final byte[] bytes = new byte[len];
        buffer.get(bytes);
        return bytes;
    }

    public static byte[] getBytes4(final MappedByteBuffer buffer) {
        return getBytes(buffer, 4);
    }

    public static byte[] getBytes8(final MappedByteBuffer buffer) {
        return getBytes(buffer, 8);
    }

    public static byte[] getBytes(final MappedByteBuffer buffer, final int offset, final int len) {
        final byte[] bytes = new byte[len];
        buffer.position(offset);
        buffer.get(bytes);
        return bytes;
    }

    public static short getUShort(final MappedByteBuffer buffer) {
        final byte[] bytes = getBytes(buffer, 2);
        short val = 0;
        for (int i = bytes.length; i > 0; --i) {
            int temp = bytes[i - 1];
            if (temp < 0) {
                temp += 256;
            }
            val = (short) (val * 256 + temp);
        }
        return val;
    }

    public static short getUShort(final MappedByteBuffer buffer, final int offset) {
        final byte[] bytes = getBytes(buffer, offset, 2);
        short val = 0;
        for (int i = bytes.length; i > 0; --i) {
            int temp = bytes[i - 1];
            if (temp < 0) {
                temp += 256;
            }
            val = (short) (val * 256 + temp);
        }
        return val;
    }

    public static String getString(final MappedByteBuffer buffer, final int len) {
        final StringBuffer sb = new StringBuffer(len);
        for (int i = 0; i < len; ++i) {
            sb.append((char) buffer.get());
        }
        return sb.toString();
    }

    public static String getString0(final MappedByteBuffer buffer, final int offset) {
        final StringBuffer sb = new StringBuffer(128);
        buffer.position(offset);
        for (byte b = buffer.get(); b != 0; b = buffer.get()) {
            sb.append((char) b);
        }
        return sb.toString();
    }

    public static String readEncString() throws IOException {
        final int len = readEncInt(true);
        if (len == 0) {
            return null;
        }
        final byte[] b = readEncByteArr(len);
        for (int i = 0; i < b.length; ++i) {
            final byte temp = b[i];
            b[i] ^= (byte) GDReader.key;
            updateKey(temp);
        }
        final String s = new String(b, GDConstants.CHARSET_STASH);
        return s;
    }

    public static class Block {
        public int length;
        public int end;
    }
}
